
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include "os.h"
#include "vfs.h"
#include "sysdef.h"

extern vfs_t __vfsRegTable_start[];
extern vfs_t __vfsRegTable_end[];

#if CONFIG_LITTLEFS
extern vfs_t __lfsRegTable_start[];
extern vfs_t __lfsRegTable_end[];
#endif

#if CONFIG_FATFS
extern vfs_t __fatfsRegTable_start[];
extern vfs_t __fatfsRegTable_end[];
#endif

static void vfs_path_name_sort(void) {
    uint32_t vfsObjNum = __vfsRegTable_end - __vfsRegTable_start;
    vfs_t * vfsRegTable = __vfsRegTable_start;
    vfs_t temp= {0};

    for(int i = 0; (vfsObjNum > 0) && (i < vfsObjNum - 1); i++) {
        for (int j = i + 1; j < vfsObjNum; j++) {
            if(strcasecmp(vfsRegTable[i].name, vfsRegTable[j].name) > 0) {
                memcpy(&temp, &vfsRegTable[i], sizeof(vfs_t));
                memcpy(&vfsRegTable[i], &vfsRegTable[j], sizeof(vfs_t));
                memcpy(&vfsRegTable[j], &temp, sizeof(vfs_t));
            }
        }
    }
}

int vfs_init(void) {
    int vsf_init_ok = 0;

    uint32_t vfsObjNum = __vfsRegTable_end - __vfsRegTable_start;
    vfs_t * vfsRegTable = __vfsRegTable_start;

    for(int i = 0; i < vfsObjNum; i++) {
        for (int j = i + 1; j < vfsObjNum; j++) {
            if(vfsRegTable[i].id == vfsRegTable[j].id) return -1;
            if(strcasecmp(vfsRegTable[i].name, vfsRegTable[j].name) == 0) return -1;
        }
    }

#if (CONFIG_LITTLEFS || CONFIG_FATFS)
    uint32_t objIdx = 0;
#endif

#if CONFIG_LITTLEFS
    uint32_t lfsObjNum = __lfsRegTable_end - __lfsRegTable_start;
    vfs_t * lfsRegTable = __lfsRegTable_start;

    vsf_init_ok = 0;

    for(objIdx = 0; objIdx < lfsObjNum; objIdx++) {
        if(vfs_lfs_init(&lfsRegTable[objIdx].u.lfs_obj, lfsRegTable[objIdx].name))
            return -1;
    }

    vsf_init_ok = 1;
#endif

#if CONFIG_FATFS
    uint32_t fatfsObjNum = __fatfsRegTable_end - __fatfsRegTable_start;
    vfs_t * fatfsRegTable = __fatfsRegTable_start;

    vsf_init_ok = 0;

    for(objIdx = 0; objIdx < fatfsObjNum; objIdx++) {
        if(vfs_fatfs_init(&fatfsRegTable[objIdx].u.fatfs_obj, fatfsRegTable[objIdx].name))
            return -1;
    }

    vsf_init_ok = 1;
#endif

    if(vsf_init_ok) {
        vfs_path_name_sort();
        return 0;
    }

    return -1;
}

static char *strnchr_r(const char *str, char chr, int n) {
    if(!str) return NULL;
    
    int len = strlen(str), ctr = 0;
    for(int i = 1; i <= len; i++) {
        if(str[len - i] == chr) {
            if(ctr == n) return (char *)str + len - i;
            ctr++;
        }
    }
    
    return NULL;
}

static char *memnchr_r(const char *start, int len, char chr, int n) {
    if(!start) return NULL;
    
    int ctr = 0;
    for(int i = 1; i <= len; i++) {
        if(start[len - i] == chr) {
            if(ctr == n) return (char *)start + len - i;
            ctr++;
        }
    }
    
    return NULL;
}

static void vfs_path_slash_optm(char *path) {
    if(path) {
        int i = 0, ref = 0;
        while(path[i]) {
            if(path[i] == '/' && path[i] == path[i+1]) ref++;
            else if(ref) {
                strcpy(path + i - ref, path + i);
                i -= ref;
                ref = 0;
            }
            i++;
        }

        if((strlen(path) > 1) && (path[strlen(path)-1] == '/')) {
            path[strlen(path)-1] = '\0';
        }
    }
}

static int vfs_path_format(char * path) {
    if(path) {
        if(path[0] == '\0') return -1;

        vfs_path_slash_optm(path);

        char *path_orig = path;
        char *forward_slash = NULL;
        char *next_forward_slash = NULL;
        char *prev_forward_slash = NULL;
        while((forward_slash = strchr(path, '/'))) {
            next_forward_slash = strchr(forward_slash + 1, '/');
            if(!next_forward_slash) {
                next_forward_slash = forward_slash + strlen(forward_slash);
            }
            char *p = NULL;
            uint8_t dot_cnt = 0;
            for(p = forward_slash + 1; p < next_forward_slash; p++) {
                if(*p != '.') break;
                dot_cnt++;
            }

            if(dot_cnt == next_forward_slash - forward_slash - 1) {
                if(dot_cnt == 0) {
                    path = forward_slash + 1;
                } else {
                    prev_forward_slash = memnchr_r(path_orig, forward_slash - path_orig + 1, '/', dot_cnt - 1);
                    if(!prev_forward_slash) {
                        prev_forward_slash = path_orig;
                    }
                    for(int i = 0; i < strlen(next_forward_slash) + 1; i++) {
                        prev_forward_slash[i] = next_forward_slash[i];
                    }
                    path = prev_forward_slash;
                }
            } else {
                path = forward_slash + 1;
            }
        }

        if(path_orig[0] == '\0') {
            path_orig[0] = '/';
            path_orig[1] = '\0';
        }

        return 0;
    }

    return -1;
}

vfs_t *vfs_get_by_path(const char *path, char **fs_path, char **abs_path) {
    uint32_t vfsObjNum = __vfsRegTable_end - __vfsRegTable_start;
    vfs_t * vfsRegTable = __vfsRegTable_start;
    char *_path = NULL, cwd[256] = {0};

    if(!path || !abs_path) return NULL;

    *abs_path = malloc(256);
    if(!*abs_path) return NULL;

    if(path[0] == '/') {
        snprintf(*abs_path, 256, "%s", path);
    } else {
        if(!getcwd(cwd, 256)) { free(*abs_path); *abs_path = NULL; return NULL;}
        snprintf(*abs_path, 256, "%s/%s", cwd, path);
    }

    vfs_path_format(*abs_path);
    _path = *abs_path + 1;

    for(int objIdx = 0; objIdx < vfsObjNum; objIdx++) {
        if(strncasecmp(_path, vfsRegTable[objIdx].name, strlen(vfsRegTable[objIdx].name)) == 0) {
            if(fs_path) {
                *fs_path = *abs_path + 1 + strlen(vfsRegTable[objIdx].name);
                if((*fs_path)[0] == '\0') (*fs_path)[0] = '/';
            } else {
                free(*abs_path);
                *abs_path = NULL;
            }
            return &vfsRegTable[objIdx];
        }
    }

    free(*abs_path);
    *abs_path = NULL;
    return NULL;
}

FILE *fopen(const char *path, const char *mode){
    char *abs_path = NULL;
    vfs_t *vfs = vfs_get_by_path(path, NULL, &abs_path);
    if(!vfs) return NULL;
    return vfs->ops->fopen(path, mode);
}

int __real_fclose(FILE *fp);
int __wrap_fclose(FILE *fp) {
    if(!fp) return -1;
    vfs_t *vfs = (vfs_t *)fp->_cookie;
    return vfs->ops->fclose(fp);
}

size_t fread(void *__restrict buf, size_t size, size_t count, FILE *__restrict fp){
    if(!fp || !buf) return -1;
    vfs_t *vfs = (vfs_t *)fp->_cookie;
    return vfs->ops->fread(buf, size, count, fp);
}

size_t fwrite(const void *__restrict buf, size_t size, size_t count, FILE * fp){
    if(!fp || !buf) return -1;
    vfs_t *vfs = (vfs_t *)fp->_cookie;
    return vfs->ops->fwrite(buf, size, count, fp);
}

int __real_fflush(FILE *fp);
int __wrap_fflush(FILE *fp) {
    if(fp == stdin || fp == stdout || fp == stderr) return __real_fflush(fp);

    if(!fp) return -1;
    vfs_t *vfs = (vfs_t *)fp->_cookie;
    return vfs->ops->fflush(fp);
}

int fseek(FILE *fp, long offset, int whence){
    if(!fp) return -1;
    vfs_t *vfs = (vfs_t *)fp->_cookie;
    return vfs->ops->fseek(fp, offset, whence);
}

long ftell(FILE * fp){
    if(!fp) return -1;
    vfs_t *vfs = (vfs_t *)fp->_cookie;
    return vfs->ops->ftell(fp);
}

void rewind(FILE * fp){
    if(!fp) return;
    vfs_t *vfs = (vfs_t *)fp->_cookie;
    return vfs->ops->rewind(fp);
}

int mkdir(const char *path, mode_t mode){
    if(!path) return -1;

    char *abs_path = NULL;
    vfs_t *vfs = vfs_get_by_path(path, NULL, &abs_path);
    if(!vfs) return -1;
    return vfs->ops->mkdir(path, mode);
}

DIR	*opendir(const char *path){
    if(!path) return NULL;

    char *abs_path = NULL;
    vfs_t *vfs = vfs_get_by_path(path, NULL, &abs_path);
    if(!vfs) return NULL;
    return vfs->ops->opendir(path);
}

int closedir(DIR *dp){
    if(!dp) return -1;
    vfs_t *vfs = (vfs_t *)dp->dd_loc;
    return vfs->ops->closedir(dp);
}

struct dirent *readdir(DIR *dp){
    if(!dp) return NULL;
    vfs_t *vfs = (vfs_t *)dp->dd_loc;
    return vfs->ops->readdir(dp);
}

void seekdir(DIR *dp, long loc){
    if(!dp) return;
    vfs_t *vfs = (vfs_t *)dp->dd_loc;
    return vfs->ops->seekdir(dp, loc);
}

long telldir(DIR *dp){
    if(!dp) return -1;
    vfs_t *vfs = (vfs_t *)dp->dd_loc;
    return vfs->ops->telldir(dp);
}

void rewinddir(DIR *dp){
    if(!dp) return;
    vfs_t *vfs = (vfs_t *)dp->dd_loc;
    return vfs->ops->rewinddir(dp);
}

int chdir(const char *path) {
    OS_ERR err;
    char cwd[256] = {0};
    int need_set_task_reg = 0;
    char *abs_path = NULL;

    if(!path) return -1;

    if(!getcwd(cwd, 256)) return -1;

    OS_REG target_wd = (OS_REG)OSTaskRegGet(NULL, OS_TLS_ID_CWD, &err);
    if(err) return -1;

    if(!target_wd) {
        target_wd = (OS_REG)malloc(256);
        if(!target_wd) return -1;

        need_set_task_reg = 1;
    }

    if(path[0] == '/') {
        snprintf((char *)target_wd, 256, "%s", path);
    } else {
        snprintf((char *)target_wd, 256, "%s/%s", cwd, path);
    }

    vfs_path_format((char *)target_wd);

    vfs_t *vfs = vfs_get_by_path((char *)target_wd, NULL, &abs_path);
    if(!vfs) {
        if(need_set_task_reg)
            free((void *)target_wd);
        return -1;
    }

    if(vfs->ops->access((char *)target_wd, 0) == 0) {
        if(need_set_task_reg) {
            OSTaskRegSet(NULL, OS_TLS_ID_CWD, target_wd, &err);
            if(err) {
                free((void *)target_wd);
                return -1;
            }
        }
    } else {
        if(need_set_task_reg)
            free((void *)target_wd);
        return -1;
    }
    
    return 0;
}

char *getcwd(char *buf, size_t size) {
    OS_ERR err;
    int dyn = 0;

    if(!size) return NULL;

    if(!buf) {
        buf = (char *)malloc(size);
        if(!buf) return NULL;
        dyn = 1;
    }

    OS_REG cwd = (OS_REG)OSTaskRegGet(NULL, OS_TLS_ID_CWD, &err);
    if(err) {
        if(dyn) free(buf);
        return NULL;
    }

    if(!cwd) {
        buf[0] = '/';
        buf[1] = '\0';
    } else {
        snprintf(buf, 256, "%s", (char *)cwd);
    }

    return buf;
}

int rmdir(const char *path){
    if(!path) return -1;

    char *abs_path = NULL;
    vfs_t *vfs = vfs_get_by_path(path, NULL, &abs_path);
    if(!vfs) return -1;
    return vfs->ops->rmdir(path);
}

int access(const char *path, int mode){
    if(!path) return -1;

    char *abs_path = NULL;
    vfs_t *vfs = vfs_get_by_path(path, NULL, &abs_path);
    if(!vfs) return -1;
    return vfs->ops->access(path, mode);
}

int stat(const char *path, struct stat *buf){
    if(!path || !buf) return -1;

    char *abs_path = NULL;
    vfs_t *vfs = vfs_get_by_path(path, NULL, &abs_path);
    if(!vfs) return -1;
    return vfs->ops->stat(path, buf);
}

int remove(const char *path){
    if(!path) return -1;

    char *abs_path = NULL;
    vfs_t *vfs = vfs_get_by_path(path, NULL, &abs_path);
    if(!vfs) return -1;
    return vfs->ops->remove(path);
}

int rename(const char *oldpath, const char *newpath){
    char *abs_path = NULL;
    FILE * old_fp = NULL, * new_fp = NULL;
    uint8_t *buf = NULL;
    int ret = 0;

    vfs_t * old_vfs = vfs_get_by_path(oldpath, NULL, &abs_path);
    if(!old_vfs) {
        ret = -1;
        goto end;
    }

    vfs_t * new_vfs = vfs_get_by_path(newpath, NULL, &abs_path);
    if(!new_vfs) {
        ret = -1;
        goto end;
    }

    if(old_vfs->type == new_vfs->type) {
        return old_vfs->ops->rename(oldpath, newpath);
    } else {
        if(access(newpath, 0) == 0) {
            ret = -1;
            goto end;
        }

        old_fp = fopen(oldpath, "rb");
        if(!old_fp) {
            ret = -1;
            goto end;
        }

        new_fp = fopen(newpath, "wb");
        if(!new_fp) {
            ret = -1;
            goto end;
        }

        buf = malloc(1024);
        if(!buf) {
            ret = -1;
            goto end;
        }

        int nread = 0;
        while((nread = fread(buf, 1, 1024, old_fp)) > 0) {
            if(fwrite(buf, 1, nread, new_fp) != nread) {
                ret = -1;
                goto end;
            }
        }

        if(nread < 0) {
            ret = -1;
            goto end;
        }
    }

end:
    if(old_fp) fclose(old_fp);
    if(new_fp) fclose(new_fp);
    if(buf) free(buf);

    if(ret != 0) {
        if(access(newpath, 0) == 0) {
            remove(newpath);
        }
        goto err;
    }

    return 0;

err:
    return -1;
}
